<?php

class CliLib
{

	public static function doCall ($name, $call, ArgContext $args)
	{
		if (!is_callable($call))
		{
			throw new CliCallException("Call '$name' is not callable");
		}

		try
		{
			return call_user_func($call, $args);
		}
		catch (CliArgumentException $ex)
		{
			$class = get_class($ex);
			$msg = "$name : " . $ex->getMessage();
			$code = $ex->getCode();

			// Throw new exception with the appropriate identifier name
			throw new $class($msg, $code);
		}
	}

	public static function isType ($value, $type)
	{
		switch ($type)
		{
			case 'value':
				return self::isValue($value);

			case 'bool':
			case 'number':
				return self::isNumber($value);

			case 'variable':
				return $value instanceof ExpVariable;

			case 'expression':
				return $value instanceof ExpressionStmt;

			case 'string':
				return is_string($value);

			case 'bind':
				return $value instanceof ExpBind;
		}

		throw new CliArgumentException("Unknown value type '$type'");
	}

	public static function numberOf ($value, CliContext $context)
	{
		if (is_int($value) || is_float($value))
		{
			return $value;
		}
		elseif (is_bool($value))
		{
			return $value ? 1 : 0;
		}
		elseif ($value instanceof ExpNumber)
		{
			return $value->getNumber($context);
		}

		self::invalidType($value, 'number');
	}

	public static function modeNumberOf ($value, CliContext $context)
	{
		if ($value instanceof ExpModeNumber)
		{
			return clone $value;
		}
		elseif (is_int($value) || is_float($value) || is_bool($value) || $value instanceof ExpNumber)
		{
			return $context->getMode()->getModeNumber(self::numberOf($value, $context));
		}

		self::invalidType($value, 'modenumber');
	}

	public static function valueOf ($value, CliContext $context)
	{
		if (is_int($value) || is_float($value) || is_bool($value) || is_string($value))
		{
			return $value;
		}
		elseif ($value instanceof ExpValue)
		{
			return $value->getValue($context);
		}

		self::invalidType($value, 'value');
	}

	public static function stringOf ($value, CliContext $context)
	{
		return (string) self::valueOf($value, $context);
	}

	public static function isTrue ($value, CliContext $context)
	{
		if (is_bool($value))
		{
			return $value;
		}
		elseif (is_int($value) || is_float($value))
		{
			return round($value) == 0 ? false : true;
		}
		elseif ($value instanceof ExpNumber)
		{
			return round(self::numberOf($value, $context)) == 0 ? false : true;
		}

		self::invalidType($value, 'bool');
	}

	public static function isNumber ($value)
	{
		return
			is_int($value) || is_float($value) || is_bool($value) ||
			$value instanceof ExpNumber;
	}

	public static function isValue ($value)
	{
		return
			is_int($value) || is_float($value) || is_bool($value) ||
			is_string($value) || $value instanceof ExpValue;
	}

	public static function invalidType ($value, $expected)
	{
		$type = is_object($value)
			? '(' . get_class($value) . ')' : gettype($value);
		throw new CliArgumentException(
			"Unexpected value type '$type', expected '$expected'");
	}

	public static function expandFloat ($string)
	{
		$string = strtolower((string) $string);

		if (!preg_match('/^([-+])?(\d*)(?:\.(\d+))?(?:e([-+]?\d+))?$/', $string, $match))
		{
			throw new CliRuntimeException(
				"Can not verify float number '$string'");
		}

		// If there is not exponent, no need to resolve anything
		if (!isset($match[4]))
		{
			return $string;
		}

		list($number, $sign, $int, $dec, $exp) = $match;

		if ($sign == '+')
		{
			$sign = '';
		}

		$num = $int . $dec;

		// Calculate new position for the decimal separator
		$pos = strlen($int);
		$pos += (int) $exp;


		if ($pos == 0)
		{
			return $sign . "0.$num";
		}
		else if ($pos < 0)
		{
			return $sign . '0.' . str_repeat('0', abs($pos)) . $num;
		}
		else
		{
			$zeros = $pos - strlen($num);

			if ($zeros == 0)
			{
				return $sign . $num;
			}
			if ($zeros < 0)
			{
				return $sign . substr($num, 0, $zeros) . '.' . substr($num, $zeros);
			}
			else
			{
				return $sign . $num . str_repeat('0', $zeros);
			}
		}
	}

}

?>